/*******************************************************************************
 * Copyright 2013 Mindaugas Idzelis
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package incontrol.server;

import static com.google.common.collect.Maps.newHashMap;
import incontrol.bf3.protocol.requests.BF3Request;
import incontrol.bf3.protocol.responses.Response;
import incontrol.bf3.state.PlayerList;
import incontrol.bf3.state.RoundState;
import incontrol.bf3.state.ServerState;
import incontrol.core.protocol.IMessage;
import incontrol.core.protocol.IPluginRequest;
import incontrol.core.protocol.requests.GetRegisteredPlugins;
import incontrol.core.protocol.requests.RegisterPluginExtension;
import incontrol.core.protocol.requests.RetrieveState;
import incontrol.core.protocol.responses.GenericResponse;
import incontrol.rcon.protocol.RCONClient;

import java.util.Map;
import java.util.Map.Entry;

import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ConfigurableServerChannel;
import org.cometd.bayeux.server.LocalSession;
import org.cometd.bayeux.server.ServerChannel;
import org.cometd.bayeux.server.ServerChannel.MessageListener;
import org.cometd.bayeux.server.ServerMessage.Mutable;
import org.cometd.bayeux.server.ServerSession;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;

public class IncontrolProtocol {

	private boolean debugReplicateClientMsgs = true;

	private Map<String, Integer> channelToPluginCount = newHashMap();
	private Map<Class<?>, MessageListener> handlers = newHashMap();
	private BayeuxServer bayeuxServer;
	private RCONClient client;
	private ServerState serverState;

	private LocalSession serverSession;

	private class BayeuxBridge implements MessageListener {

		@Override
		public boolean onMessage(final ServerSession from,
				final ServerChannel channel, Mutable message) {
			boolean deliver = true;
			Object data = message.getData();
			if (data instanceof IMessage) {
				IMessage msg = (IMessage) message.getData();
				Object o = msg.getData();
				if (o instanceof IPluginRequest) {
					// this always are sent to all clients
				}
				for (Entry<Class<?>, MessageListener> entry : handlers
						.entrySet()) {
					if (o.getClass().isAssignableFrom(entry.getKey())) {
						boolean d = entry.getValue().onMessage(from, channel,
								message);
						if (!d)
							deliver = false;
					}
				}
			}
			if (debugReplicateClientMsgs)
				return true;
			return deliver;
		}
	}

	private class BF3Handler implements MessageListener {
		@Override
		public boolean onMessage(final ServerSession from,
				final ServerChannel channel, Mutable message) {
			BF3Request request = (BF3Request) message.getData();

			Futures.addCallback(client.send(request),
					new FutureCallback<Response>() {
						public void onSuccess(Response result) {
							Mutable m = bayeuxServer.newMessage();
							m.setChannel(channel.getId());
							if (debugReplicateClientMsgs) {
								bayeuxServer.getChannel(channel.getId())
										.publish(serverSession, m);
							} else {
								bayeuxServer.getSession(from.getId()).deliver(
										serverSession, m);
							}
						};

						@Override
						public void onFailure(Throwable t) {

						}
					});
			return false;

		}
	}

	public class GetRegisteredPluginsHandler implements MessageListener {

		@Override
		public boolean onMessage(ServerSession from, ServerChannel channel,
				Mutable message) {
			IMessage o = (IMessage) message.getData();
			GetRegisteredPlugins state = ((GetRegisteredPlugins) o.getData());
			Integer count = channelToPluginCount.get(state.getChannelName());
			if (count == null)
				count = 0;
			GenericResponse r = new GenericResponse(count);
			IMessage holder = new IMessage(r, o.getId(), null);
			Mutable mutable = bayeuxServer.newMessage();
			mutable.setData(holder);
			mutable.setChannel(channel.getId());
			mutable.setId(message.getId());

			if (debugReplicateClientMsgs) {
				bayeuxServer.getChannel(channel.getId()).publish(serverSession,
						mutable);
			} else {
				bayeuxServer.getSession(from.getId()).deliver(serverSession,
						mutable);
			}
			return false;
		}

	}

	public class RegisterPluginExtensionHandler implements MessageListener {

		@Override
		public boolean onMessage(ServerSession from, ServerChannel channel,
				Mutable message) {
			IMessage o = (IMessage) message.getData();
			RegisterPluginExtension state = ((RegisterPluginExtension) o
					.getData());
			Integer count = channelToPluginCount.get(state.getChannelName());
			if (count == null)
				count = 1;
			else
				count = count.intValue() + 1;
			channelToPluginCount.put(state.getChannelName(), count);

			GenericResponse r = new GenericResponse(count);
			IMessage holder = new IMessage(r, o.getId(), null);
			Mutable mutable = bayeuxServer.newMessage();
			mutable.setData(holder);
			mutable.setChannel(channel.getId());
			mutable.setId(message.getId());

			if (debugReplicateClientMsgs) {
				bayeuxServer.getChannel(channel.getId()).publish(serverSession,
						mutable);
			} else {
				bayeuxServer.getSession(from.getId()).deliver(serverSession,
						mutable);
			}
			return false;
		}

	}

	private class RetrieveStateHandler implements MessageListener {

		public RetrieveStateHandler() {
		}

		@Override
		public boolean onMessage(ServerSession from, ServerChannel channel,
				Mutable message) {

			IMessage o = (IMessage) message.getData();
			RetrieveState state = ((RetrieveState) o.getData());

			switch (state.getType()) {
			case PLAYER: {
				Mutable mutable = bayeuxServer.newMessage();
				PlayerList playerList = serverState.getPlayerList();
				GenericResponse r = new GenericResponse(playerList.byName()
						.get(state.getPlayerName()));
				IMessage holder = new IMessage(r, o.getId(), null);
				mutable.setData(holder);
				mutable.setChannel(channel.getId());
				mutable.setId(message.getId());
				if (debugReplicateClientMsgs) {
					bayeuxServer.getChannel(channel.getId()).publish(
							serverSession, mutable);
				} else {
					bayeuxServer.getSession(from.getId()).deliver(
							serverSession, mutable);
				}
				break;
			}
			case PLAYERLIST: {
				Mutable mutable = bayeuxServer.newMessage();
				PlayerList playerList = serverState.getPlayerList();
				GenericResponse r = new GenericResponse(playerList);
				IMessage holder = new IMessage(r, o.getId(), null);
				mutable.setData(holder);
				mutable.setChannel(channel.getId());
				mutable.setId(message.getId());
				if (debugReplicateClientMsgs) {
					bayeuxServer.getChannel(channel.getId()).publish(
							serverSession, mutable);
				} else {
					bayeuxServer.getSession(from.getId()).deliver(
							serverSession, mutable);
				}
				break;
			}
			case ROUND: {
				Mutable mutable = bayeuxServer.newMessage();
				RoundState round = serverState.getRound();
				GenericResponse r = new GenericResponse(round);
				IMessage holder = new IMessage(r, o.getId(), null);
				mutable.setData(holder);
				mutable.setChannel(channel.getId());
				mutable.setId(message.getId());
				if (debugReplicateClientMsgs) {
					bayeuxServer.getChannel(channel.getId()).publish(
							serverSession, mutable);
				} else {
					bayeuxServer.getSession(from.getId()).deliver(
							serverSession, mutable);
				}
				break;
			}
			case SERVER: {
				Mutable mutable = bayeuxServer.newMessage();
				GenericResponse r = new GenericResponse(serverState);
				IMessage holder = new IMessage(r, o.getId(), null);
				mutable.setData(holder);
				mutable.setChannel(channel.getId());
				mutable.setId(message.getId());
				if (debugReplicateClientMsgs) {
					bayeuxServer.getChannel(channel.getId()).publish(
							serverSession, mutable);
				} else {
					bayeuxServer.getSession(from.getId()).deliver(
							serverSession, mutable);
				}
				break;
			}
			}

			return false;
		}
	}

	public void init(final RCONClient client, final ServerState serverState,
			final BayeuxServer bayeuxServer, LocalSession serverSession) {

		this.client = client;
		this.serverState = serverState;
		this.bayeuxServer = bayeuxServer;
		this.serverSession = serverSession;
		bayeuxServer.addListener(new BayeuxServer.ChannelListener() {
			private BayeuxBridge bridge = new BayeuxBridge();

			@Override
			public void configureChannel(ConfigurableServerChannel channel) {

			}

			@Override
			public void channelRemoved(String channelId) {

			}

			@Override
			public void channelAdded(ServerChannel channel) {
				if (!channel.isWild() && channel.isBroadcast()) {
					channel.addListener(bridge);
				}
			}
		});

		addHandler(RetrieveState.class, new RetrieveStateHandler());
		addHandler(BF3Request.class, new BF3Handler());
		addHandler(GetRegisteredPlugins.class,
				new GetRegisteredPluginsHandler());
		addHandler(RegisterPluginExtension.class,
				new RegisterPluginExtensionHandler());
	}

	private void addHandler(Class<?> msgClazz, MessageListener listener) {
		handlers.put(msgClazz, listener);

	}
}
